/*
 * CBaseStats.java
 * 
 * Copyright (c) 2009 Giles Millward (Manigra). All rights reserved.
 * 
 * This file is part of JRogueDPS.
 * 
 * JRogueDPS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * JRogueDPS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with JRogueDPS.  If not, see <http://www.gnu.org/licenses/>.
 */

package dpssystem;

import dpsinterface.CDPSInterface;
import dpsinterface.CDPSRaidBuff;
import dpsinterface.CDPSTalents;
import dpssystem.CWeapon.WeaponType;
import geardatabase.CStats;

/**
 *
 * @author Giles Millward (Manigra)
 */
public class CBaseStats {
    CStats m_coreStats;
    CDPSTalents m_talents;
    CDPSRaidBuff m_raidBuffs;
    CDPSInterface m_dpsInterface;

    private int m_health;
    private double m_agility;
    private double m_strength;
    private int m_stamina;
    private double m_attackPower;
    private int m_hitRating;
    private double m_hitChance;
    private double m_spellHitChance;
    private int m_critRating;
    private double m_critChance;
    private double m_OHCritChance;
    private double m_MHCritChance;
    private double m_spellCritChance;
    private int m_armorPenRating;
    private double m_armorPenPercent;
    private int m_expertiseRating;
    private double m_expertiseDodge;
    private int m_hasteRating;
    private double m_hastePercent;
    private double m_lightningReflexes;

    public CBaseStats(CDPSInterface p_dpsInterface) {
        m_dpsInterface = p_dpsInterface;
        m_coreStats = new CStats();
        m_coreStats.clone(p_dpsInterface.m_coreStats.m_coreStats);
        m_talents = p_dpsInterface.m_talents;
        m_raidBuffs = p_dpsInterface.m_raidBuffs;

        m_health = 7424 + (m_coreStats.m_stamina * 10);
        m_stamina = m_coreStats.m_stamina;

        // Important that following are calculated in the correct order
        this.calculateStrength();
        this.calculateAgility();
        this.calculateAttackPower();
        this.calculateHit();
        this.calculateCrit();
        this.calculateArmorPen();
        this.calculateExpertise();

        // Lightning reflexes has weird talent point distribution
        int l_lightningReflexes = m_talents.getLightningReflexes();
        if (l_lightningReflexes == 1)
        {
            m_lightningReflexes = 0.04;
        } else if (l_lightningReflexes == 2)
        {
            m_lightningReflexes = 0.07;
        } else if (l_lightningReflexes == 3)
        {
            m_lightningReflexes = 0.10;
        } else
        {
            m_lightningReflexes = 0.00;
        }

        this.calulcateHaste();
        // this.logStats();
    }

    private void logStats()
    {
        System.out.println("Static stats");
        System.out.println("Health " + m_health);
        System.out.println("Agility " + m_agility);
        System.out.println("Strength " + m_strength);
        System.out.println("Stamina " + m_stamina);
        System.out.println("Attack Power " + m_attackPower);
        System.out.println("HitRating " + m_hitRating);
        System.out.println("HitChance " + m_hitChance);
        System.out.println("SpellHitChance " + m_spellHitChance);
        System.out.println("CritRating " + m_critRating);
        System.out.println("CritChance " + m_critChance);
        System.out.println("MH CritChance " + m_MHCritChance);
        System.out.println("OH CritChance " + m_OHCritChance);
        System.out.println("SpellCritChance " + m_spellCritChance);
        System.out.println("ArmorPenRating " + m_armorPenRating);
        System.out.println("ArmorPenPercent " + m_armorPenPercent);
        System.out.println("ExpertiseRating " + m_expertiseRating);
        System.out.println("ExpertiseDodge " + m_expertiseDodge);
        System.out.println("HasteRating " + m_hasteRating);
        System.out.println("HastePercent " + m_hastePercent);
    }

    // Update the base stats
    public void add(CStats p_stats)
    {
        this.adjust(p_stats, 1);
    }

    public void subtract(CStats p_stats)
    {
        this.adjust(p_stats, -1);
    }

    public void adjust(CStats p_stats, int p_sign)
    {
        if (p_stats.m_agility != 0)
        {
            // update agility
            m_coreStats.m_agility += (p_stats.m_agility * p_sign);
            this.calculateAgility();
            this.calculateAttackPower();
            this.calculateCrit();
        }

        if (p_stats.m_armorPenRating != 0)
        {
            // update armor Pen Rating
            m_coreStats.m_armorPenRating += (p_stats.m_armorPenRating * p_sign);
            this.calculateArmorPen();
        }

        if (p_stats.m_attackPower != 0)
        {
            // update attack power
            m_coreStats.m_attackPower += (p_stats.m_attackPower * p_sign);
            this.calculateAttackPower();
        }

        if (p_stats.m_critRating != 0)
        {
            m_coreStats.m_critRating += (p_stats.m_critRating * p_sign);
            this.calculateCrit();
        }

        if (p_stats.m_expertiseRating != 0)
        {
            m_coreStats.m_expertiseRating += (p_stats.m_expertiseRating * p_sign);
            this.calculateExpertise();
        }

        if (p_stats.m_hasteRating != 0)
        {
            m_coreStats.m_hasteRating += (p_stats.m_hasteRating * p_sign);
            this.calulcateHaste();
        }

        if (p_stats.m_hitRating != 0)
        {
            m_coreStats.m_hitRating += (p_stats.m_hitRating * p_sign);
            this.calculateHit();
        }

        if (p_stats.m_strength != 0)
        {
            m_coreStats.m_strength += (p_stats.m_strength * p_sign);
            this.calculateStrength();
            this.calculateAgility();
            this.calculateAttackPower();
            this.calculateCrit();
        }
    }

    private void calculateArmorPen()
    {
        m_armorPenRating = m_coreStats.m_armorPenRating;
        m_armorPenPercent = CUtil.getArmorPenRatingToArmorReduction(m_armorPenRating);        
    }
    
    private void calculateExpertise()
    {
        m_expertiseRating = m_coreStats.m_expertiseRating;
        m_expertiseDodge = CUtil.getExpToDodgeParryReduction(
            CUtil.getExpRatingToExp(m_expertiseRating) + ((double) m_talents.getWeaponExpertise()) * 5.0) * 0.01;
    }

    private void calculateStrength()
    {
        // Strength = (race stats + gear stats + (motw (52)) + (Agi/Str (178)) + (agility food (25) + procs)) *
        // (1.0 + Bok multiplier (0.10))
        m_strength = m_coreStats.m_strength;
        m_strength *= (1.0 + m_raidBuffs.getStatsMult());
    }

    private void calculateAgility()
    {
        // Agility = (Gear + (Agility/Str buff) + (motw 52) + (Agility Food 40) + proc) *
        //           (1 + sinister calling * 3) * (1 + Blessing of Kings (10%))
        m_agility = m_coreStats.m_agility;
        m_agility *= (1.0 + m_talents.getSinisterCallingDouble() * 3) *
                     (1.0 + m_raidBuffs.getStatsMult());
    }

    private void calculateAttackPower()
    {
        // Attack power = strength + agility +
        //    floor(gear attack power + 140 + attack power flat (688) + attack power food + flask of endless rage) *
        //    (1 + Deadlyness + Savage Combat) * (1 + Attack Power Multiplier(10%));
        m_attackPower = m_agility +
                        m_strength +
                        m_coreStats.m_attackPower;
        m_attackPower *= ((1.0 + ((m_talents.getSavageCombatDouble() * 2.0) + (m_talents.getDeadlinessDouble() * 2.0))) *
                         (1.0 + m_raidBuffs.getAttackPowerMult()));
    }

    private void calculateHit()
    {
        // Both Physical Hit and Spell Hit
        double l_precisionTalent = (double) m_talents.getPrecision();

        m_hitRating = m_coreStats.m_hitRating;
        m_hitChance = CUtil.getHitRatingToHit(m_hitRating) + l_precisionTalent;
        if (m_raidBuffs.isDraenei()) {
            m_hitChance += 1.0;
        }

        m_hitChance *= 0.01;

        m_spellHitChance = CUtil.getHitRatingToSpellHit(m_hitRating) + l_precisionTalent;
        if (m_raidBuffs.isSpellHit()) {
            m_spellHitChance += 3.0;
        }

        if (m_raidBuffs.isDamage()) {
            m_spellHitChance += 1.0;
        }

        m_spellHitChance *= 0.01;
    }


    private void calculateCrit()
    {
        // Do phys crit and spell crit

        // =(crit rating /(14*3.279)+ malice + (agi / 83.333) - 0.295)/100 + lotp + hotc - (8*0.006)
        m_critRating = m_coreStats.m_critRating;
        m_critChance = ((CUtil.getCritRatingToCrit(m_critRating) +
                (double) m_talents.getMalice() +
                CUtil.getAgiToCrit(m_agility) +
                -0.295) * 0.01) +
                m_raidBuffs.getMeleeCritBuff() +
                m_raidBuffs.getPhysicalCrit(); // TODO Watch out for master poisoner being a raid buff & this rogue talented in it

        m_MHCritChance = m_OHCritChance = m_critChance;
        WeaponType l_mainHand = this.m_dpsInterface.m_MHWeapon.getWeaponType();
        WeaponType l_offHand = this.m_dpsInterface.m_OHWeapon.getWeaponType();
        if (l_mainHand == WeaponType.FIST || l_mainHand == WeaponType.DAGGER) {
            m_MHCritChance += (double) m_talents.getCloseQuartersCombatDouble();
        }

        if (l_offHand == WeaponType.FIST || l_offHand == WeaponType.DAGGER) {
            m_OHCritChance += (double) m_talents.getCloseQuartersCombatDouble();
        }

        // Spell Crit = crit_rating/45.90958/100 + critbuff (3%) + spell crit buff(5%) + moonkin buff(5%) - 2.1%
        m_spellCritChance = (CUtil.getCritRatingToSpellCrit(m_critRating) * 0.01) +
                            m_raidBuffs.getPhysicalCrit() + // 3%
                            m_raidBuffs.getSpellCrit() + // 2%
                            m_raidBuffs.getSpellCritBuff() + // 5%
                            - 0.021; // crit depression of 2.1%
    }

    private void calulcateHaste()
    {
        // Haste = 1 + (hasteRating / 32.79 / 100) * (1 + WF) * (1 + RetrBuff) * (Lightning reflexes (0/4/7/10)
        m_hasteRating = m_coreStats.m_hasteRating;
        m_hastePercent = 1.0 - (CUtil.getHasteRatingToHaste(m_hasteRating) * 0.01);
        m_hastePercent *= (1.0 / (1.0 + m_raidBuffs.getMeleeHaste()));
        m_hastePercent *= (1.0 / (1.0 + m_raidBuffs.getHaste()));
        m_hastePercent *= (1.0 / (1.0 + m_lightningReflexes));
    }
    /**
     * @return the m_agility
     */
    public double getAgility() {
        return m_agility;
    }

    /**
     * @param m_agility the m_agility to set
     */
    public void setAgility(double m_agility) {
        this.m_agility = m_agility;
    }

    /**
     * @return the m_attackPower
     */
    public double getAttackPower() {
        return m_attackPower;
    }

    /**
     * @param m_attackPower the m_attackPower to set
     */
    public void setAttackPower(double m_attackPower) {
        this.m_attackPower = m_attackPower;
    }

    /**
     * @return the m_hitRating
     */
    public int getHitRating() {
        return m_hitRating;
    }

    /**
     * @param m_hitRating the m_hitRating to set
     */
    public void setHitRating(int m_hitRating) {
        this.m_hitRating = m_hitRating;
    }

    /**
     * @return the m_hitChance
     */
    public double getHitChance() {
        return m_hitChance;
    }

    /**
     * @param m_hitChance the m_hitChance to set
     */
    public void setHitChance(double m_hitChance) {
        this.m_hitChance = m_hitChance;
    }

    /**
     * @return the m_spellHitChance
     */
    public double getSpellHitChance() {
        return m_spellHitChance;
    }

    /**
     * @param m_spellHitChance the m_spellHitChance to set
     */
    public void setSpellHitChance(double m_spellHitChance) {
        this.m_spellHitChance = m_spellHitChance;
    }

    /**
     * @return the m_critRating
     */
    public int getCritRating() {
        return m_critRating;
    }

    /**
     * @param m_critRating the m_critRating to set
     */
    public void setCritRating(int m_critRating) {
        this.m_critRating = m_critRating;
    }

    /**
     * @return the m_critChance
     */
    public double getCritChance() {
        return m_critChance;
    }

    /**
     * @param m_critChance the m_critChance to set
     */
    public void setCritChance(double m_critChance) {
        this.m_critChance = m_critChance;
    }

    /**
     * @return the m_spellCritChance
     */
    public double getSpellCritChance() {
        return m_spellCritChance;
    }

    /**
     * @param m_spellCritChance the m_spellCritChance to set
     */
    public void setSpellCritChance(double m_spellCritChance) {
        this.m_spellCritChance = m_spellCritChance;
    }

    /**
     * @return the m_armorPenRating
     */
    public int getArmorPenRating() {
        return m_armorPenRating;
    }

    /**
     * @param m_armorPenRating the m_armorPenRating to set
     */
    public void setArmorPenRating(int m_armorPenRating) {
        this.m_armorPenRating = m_armorPenRating;
    }

    /**
     * @return the m_expertiseRating
     */
    public int getExpertiseRating() {
        return m_expertiseRating;
    }

    /**
     * @param m_expertiseRating the m_expertiseRating to set
     */
    public void setExpertiseRating(int m_expertiseRating) {
        this.m_expertiseRating = m_expertiseRating;
    }

    /**
     * @return the m_expertiseDodge
     */
    public double getExpertiseDodge() {
        return m_expertiseDodge;
    }

    /**
     * @param m_expertiseDodge the m_expertiseDodge to set
     */
    public void setExpertiseDodge(double m_expertiseDodge) {
        this.m_expertiseDodge = m_expertiseDodge;
    }

    /**
     * @return the m_hasteRating
     */
    public int getHasteRating() {
        return m_hasteRating;
    }

    /**
     * @param m_hasteRating the m_hasteRating to set
     */
    public void setHasteRating(int m_hasteRating) {
        this.m_hasteRating = m_hasteRating;
    }

    /**
     * @return the m_hastePercent
     */
    public double getHastePercent() {
        return m_hastePercent;
    }

    /**
     * @param m_hastePercent the m_hastePercent to set
     */
    public void setHastePercent(double m_hastePercent) {
        this.m_hastePercent = m_hastePercent;
    }

    /**
     * @return the m_armorPenPercent
     */
    public double getArmorPenPercent() {
        return m_armorPenPercent;
    }
}
